/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.loaders;
import java.beans.*;
import java.util.*;
import javax.swing.SwingUtilities;
import org.openide.filesystems.*;
import org.openide.util.Mutex;
import org.openide.util.WeakListener;
import org.openide.util.RequestProcessor;
import org.openide.util.Task;
import org.openide.util.TaskListener;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
/** Watches over a folder and represents its
* child data objects by nodes.
*
* @author Jaroslav Tulach
*/
final class FolderChildren extends Children.Keys implements PropertyChangeListener {
/** the folder */
private DataFolder folder;
/** filter of objects */
private DataFilter filter;
/** initialization of children task */
private Task initTask;
/** listener on changes in nodes */
private PropertyChangeListener listener;
/**
* @param f folder to display content of
* @param map map to use for holding of children
*/
public FolderChildren (DataFolder f) {
this (f, DataFilter.ALL);
}
/**
* @param f folder to display content of
* @param filter filter of objects
*/
public FolderChildren (DataFolder f, DataFilter filter) {
this.folder = f;
this.filter = filter;
this.listener = WeakListener.propertyChange (this, folder);
}
/** If the folder changed its children we change our nodes.
*/
public void propertyChange (final PropertyChangeEvent ev) {
if (DataFolder.PROP_CHILDREN.equals (ev.getPropertyName ())) {
refreshChildren ();
return;
}
if (
DataFolder.PROP_SORT_MODE.equals (ev.getPropertyName ()) ||
DataFolder.PROP_ORDER.equals (ev.getPropertyName ())
) {
refreshChildren ();
return;
}
}
/** Refreshes the children.
*/
void refreshChildren () {
initialize (true, false);
}
/** Refreshes the children.
* @param ch collection of children data objects
*/
void refreshChildren (List ch) {
ListIterator it = ch.listIterator ();
LinkedList l = new LinkedList ();
while (it.hasNext ()) {
DataObject obj = (DataObject)it.next ();
l.add (createKey (obj));
}
setKeys (l);
}
/** Creates a key for given data object.
* This method is here to create something different then data object,
* because the data object should be finalized when not needed and
* that is why it should not be used as a key.
*
* @param obj data object
* @return key representing the data object.
*/
private static Object createKey (DataObject obj) {
return new Pair (obj.getLoader (), obj.getPrimaryFile ());
}
/** This method takes the key created by createKey and converts it
* into primary file.
*
* @param key the key
* @return primary file of the key
*/
private static FileObject getFile (Object key) {
return ((Pair)key).primaryFile;
}
/** Create a node for one data object.
* @param key DataObject
*/
protected Node[] createNodes (Object key) {
FileObject fo = getFile (key);
DataObject obj;
try {
obj = DataObject.find (fo);
if (filter == null || filter.acceptDataObject (obj)) {
return new Node[] { obj.getClonedNodeDelegate (filter) };
} else {
return new Node[0];
}
} catch (DataObjectNotFoundException e) {
return new Node[0];
}
}
/** Improves the searching capability to wait till all children
* are found and then searching thru them.
*/
public Node findChild (final String name) {
// start the initialization
Node[] forget = getNodes ();
// waits till the list of children is created
initialize (false, false).waitFinished ();
Node node = super.findChild (name);
return node;
}
/** Initializes the children.
*/
protected void addNotify () {
initialize (true, true);
// add as a listener for changes on nodes
folder.addPropertyChangeListener (listener);
}
/** Deinitializes the children.
*/
protected void removeNotify () {
// removes the listener
folder.removePropertyChangeListener (listener);
setKeys (java.util.Collections.EMPTY_SET);
}
/** Ensures that the content of children will be filled sometime.
* @param force true if the content should be filled immediatelly
*/
private Task initialize (boolean force, boolean waitFirst) {
Task t = initTask;
if (t != null && !force) {
return t;
}
final Addition add = new Addition (waitFirst);
initTask = t = folder.computeChildrenList (add);
t.addTaskListener (add);
if (waitFirst) {
add.waitFirst ();
}
return t;
}
/** Display name */
public String toString () {
return folder.getPrimaryFile ().toString ();
}
/** time delay between two inserts of nodes */
private static final int TIME_DELAY = 256;
/** Support for incremental adding of new nodes.
*
* <P>
* There is a deadlock warning:
* <OL>
* <LI>A thread waiting in the waitFirst method can have MUTEX.readAccess
* <LI>Thread running run () needs access to MUTEX.writeAccess (because of setKeys)
* <LI>Be sure that the thread leaves waitFirst before writeAccess is needed
* </OL>
*/
private class Addition extends Object
implements TaskListener, FolderListListener {
static final long serialVersionUID =-4194617547214845940L;
/** last time of addition */
private long time = System.currentTimeMillis () + TIME_DELAY;
/** delay */
private int delay = TIME_DELAY;
/** update the nodes during processing or only at the end */
private boolean processingUpdate;
/** @param processingUpdate update the nodes during
* processing or only at the end
*/
public Addition (boolean processingUpdate) {
this.processingUpdate = processingUpdate;
}
/** Another object has been recognized.
* @param obj the object recognized
* @param arr array where the implementation should add the
* object
*/
public void process(DataObject obj, java.util.List arr) {
if (!filter.acceptDataObject (obj)) {
return;
}
// first accepted object is notified to the waiting thread in
boolean first = arr.isEmpty ();
arr.add (obj);
if (!processingUpdate) {
// if we should not notify during processing update
// skip the rest
return;
}
if (first) {
synchronized (this) {
notify ();
}
refreshChildren (arr);
return;
}
if (System.currentTimeMillis () > time) {
if (!arr.isEmpty ()) {
// add the nodes
refreshChildren (arr);
delay *= 2;
}
time = System.currentTimeMillis () + delay;
}
}
/** All objects has been recognized.
* @param arr list of DataObjects
*/
public void finished(java.util.List arr) {
synchronized (this) {
notify ();
}
// change the order because initialize method has already finished
refreshChildren (arr);
}
/** Getter for first map.
*/
public synchronized void waitFirst () {
try {
wait (50);
} catch (InterruptedException e) {
// cannot happen
throw new InternalError ();
}
}
/** Called when a task finishes running.
* @param task the finished task
*/
public void taskFinished(Task task) {
initTask = Task.EMPTY;
}
}
/** Pair of loader and primary file.
*/
private static final class Pair extends Object {
public DataLoader loader;
public FileObject primaryFile;
public Pair (DataLoader loader, FileObject primaryFile) {
this.loader = loader;
this.primaryFile = primaryFile;
}
public int hashCode () {
return loader.hashCode () + 2 * primaryFile.hashCode ();
}
public boolean equals (Object o) {
if (o instanceof Pair) {
Pair p = (Pair)o;
return loader == p.loader && primaryFile.equals (p.primaryFile);
}
return false;
}
}
}
/*
* Log
* 33 Gandalf 1.32 1/9/00 Jaroslav Tulach When refreshing content
* of folders the sibling nodes are not collapsed.
* 32 Gandalf 1.31 1/8/00 Jaroslav Tulach Works better.
* 31 Gandalf 1.30 12/2/99 Jaroslav Tulach Refresh of content of
* folder is now done in special request processor
* 30 Gandalf 1.29 11/5/99 Jaroslav Tulach WeakListener has now
* registration methods.
* 29 Gandalf 1.28 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 28 Gandalf 1.27 10/5/99 Jaroslav Tulach Heap debugging
* improvement.
* 27 Gandalf 1.26 9/30/99 Jaroslav Tulach ClassCastEx fix.
* 26 Gandalf 1.25 9/28/99 Jaroslav Tulach Changes in loader pool
* are reflected in repository.
* 25 Gandalf 1.24 9/22/99 Jaroslav Tulach addNotify really works.
* 24 Gandalf 1.23 9/13/99 Jaroslav Tulach #3730
* 23 Gandalf 1.22 9/1/99 Jaroslav Tulach Mutex.postWriteRequest
* 22 Gandalf 1.21 8/30/99 Jaroslav Tulach Less deadlocks?
* 21 Gandalf 1.20 8/27/99 Jaroslav Tulach Short waiting.
* 20 Gandalf 1.19 8/27/99 Jaroslav Tulach New threading model &
* Children.
* 19 Gandalf 1.18 8/18/99 Ian Formanek Generated serial version
* UID
* 18 Gandalf 1.17 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 17 Gandalf 1.16 5/25/99 Jaroslav Tulach Fix #1889
* 16 Gandalf 1.15 4/24/99 Jaroslav Tulach
* 15 Gandalf 1.14 4/23/99 Jaroslav Tulach Does not ignore filters.
* 14 Gandalf 1.13 4/22/99 Jaroslav Tulach Timeout when waiting for
* first child.
* 13 Gandalf 1.12 4/21/99 Jaroslav Tulach DataObjects can be
* finalized
* 12 Gandalf 1.11 4/17/99 Jaroslav Tulach Works better with empty
* folders
* 11 Gandalf 1.10 4/16/99 Jaroslav Tulach Changes in children.
* 10 Gandalf 1.9 4/8/99 Jaroslav Tulach fix 1441
* 9 Gandalf 1.8 2/18/99 Jaroslav Tulach Lazy initialization of
* order of data objects in the folder.
* 8 Gandalf 1.7 2/17/99 Jaroslav Tulach Faster setOrder/getOrder
* 7 Gandalf 1.6 2/16/99 Jaroslav Tulach Better usage of
* WeakListeners
* 6 Gandalf 1.5 2/11/99 Jaroslav Tulach SystemAction is
* javax.swing.Action
* 5 Gandalf 1.4 2/5/99 Jaroslav Tulach
* 4 Gandalf 1.3 2/4/99 Jaroslav Tulach Properties and explorer
* 3 Gandalf 1.2 2/4/99 Jaroslav Tulach Compiles with javac
* 2 Gandalf 1.1 2/3/99 Jaroslav Tulach Recognizing of folder
* data object on background
* 1 Gandalf 1.0 1/5/99 Ian Formanek
* $
*/